Python WSGI Apps + Gunicorn HTTP Server + Nginx
Webserver
2017년 11월 15일 수요일
오후 4:34
파이썬으로
어플리케이션을 만들었다.
웹을 통해서
서비스를 하려면 어떻게 해야할까?
초 간단버전으로 정리해보면 1) 파이썬 어플리케이션을 만들고 2) 이것을 구동할 웹 어플리케이션 서버와 연결시키고 3) 이왕이면 그 앞에 (=바깥세상과의
연결통로로) 웹서버를 둔다 |
0) 기술스택 참고
- LAMP: Linux Apache MySQL PHP
- LEMP: Linux Nginx MySQL PHP
1) 맨 앞에서 request 를 받아들이는게 Nginx 웹서버 (보통 Apache 또는 Nginx)
- Static 한 것들을
처리하거나
- DDOS 등을 대응하거나
- 기타등등 많은 일을
한다.
2) 이처럼 단순 작업이
아닌 것을 처리하는게 웹 어플리케이션 서버
- Gunicorn 또는 uWSGI 가 유명
- Gunicorn 은
간편하고 탄탄하다. uWSGI는 기능막강하고 대세이다 정도로 요약
3) 웹어플리케이션 서버를
통해 우리가 개발한 웹앱을 구동시킨다
- 그냥 코딩할 수도 있고
- (파이썬의 경우) Flask, Django 와 같은 Framework 를 이용해서
좀더 간편하게 개발할 수도 있다
|
1) 외부에서 Request 가
온다 2) Nginx 는 - Static 한
것들은 알아서 처리하고 - 웹앱서버가 처리할
것은 - 로컬호스트의 특정
포트로 보내준다 3) 그러면 Gunicorn 이
받아서 - Flask (그림의
고추)로 구현된 앱에서 처리 |
|
1) 외부 웹브라우저에서 HTTP로 Request를 날리면 2) Nginx 같은 웹서버가 Socket 을
통해서 WSGUI 로 보냄 - WSGI: Web Server Gateway Interface 3) 그러면 Gunicorn 과
같은 WSGI Server 가 - WSGI 방식으로
앱과 연동 * Gunicorn 이 Nginx 웹서버와 App 사이에서 - Socket, WSGI 와 같은 통신방법으로 - 업무를 처리한다고
보자 |
|
이 그림도 마찬가지다 1) Nginx 는 웹서버이고 Static
Asset을 처리 2) Guncorn을 통해 탑재된 Django
앱과 통신 |
|
Gunicorn, uWSGI 같은 WSGI server에 - Django 와
같은 앱이 탑재된다 |
- stand-alone WSGI web application server
- 기본적으로 다양한 프레임워크를
지원한다 (with adapters)
- 엄청 쉬워서 개발시의
서버를 쉽게 대체할 수 있다.
Ruby Application을
위한 Unicorn Web server 와 상당히 유사하며,
pre-fork model 이다. = central 의
Gunicorn master가 worker의 관리를 한다.
(creating sockets, bindings, etc)
- 고성능 웹서버 / (reverse) proxy
- 가볍고 쉽고 확장까지
쉽다 (add-ons, plug-ins)
- 거의 무제한의 request를 handling 가능하다.
* 리버스 프록시(reverse proxy) : 프록시 서버가 사설 네트워크(private network)
상의 서버들 바로 앞단의 프론트엔드(front-end)에 위치하여 서버들을 제어하고
보호 |
- (Gunicorn을
포함한) application server 들도 static file
를 제공하고 response 할 수 있다.
- 하지만 static 들 (javascript, css, images, etc)은
그냥 (reverse-proxy) server 가 처리하게 하는게 좋다.
- 이런 경우 Nginx 는
1) static files 를
다루고
2) connection을 managing 한다. (requests)
= 앱서버의 부담을 덜어준다
많은 connection을 동시에 감당하는 딴딴한 (robust) 구조로
만들려면 여러 서버에 분산해놓으려 할 것이다. = VPS
- 그리고 이런 VPS 앞에 Nginx 와 같은
reverse-proxy server를 두려 할것이다.
* VPS (Virtual private server)
: http://library.gabia.com/infrahosting_3
간단한
구조는 아래와 같다.
- Client가 request 를 날리면
- Nginx가 받아서
아래의 worker 들에게 나눠주는 것이다. (Port들이
다른걸로 구분)
여기서부터 실제로 해보자
웹콘솔에서 VM 생성 |
- VM 만들기 |
sudo apt-get update &&
sudo apt-get install python-pip sudo apt-get install virtualenv mkdir my_app && cd
my_app virtualenv my_app_venv source my_app_venv/bin/activate |
- 폴더 + 가상환경 생성 및 구동 |
sudo apt-get install nginx |
- 웹서버 nginx 설치 - 웹어플리케이션서버 gunicorn 설치 |
vim myapplication.py gunicorn --workers 4 --bind
127.0.0.2:8080 myapplication |
파이썬 앱 만들고 실행 |
|
|
|
|
|
|
|
|
|
1) 이름은 Nginx 2) 지역은
us-central1-b (지역마다 가격이 다를 수 있다) 3) VM의 HTTP port 80을
열어줘야 외부에서 들어올 수 있다 |
- 웹콘솔에서 SSH로 접속
sudo apt-get update &&
sudo apt-get install python-pip sudo apt-get install virtualenv |
1) pip 설치 2) virtualenv 설치 |
mkdir my_app && cd
my_app virtualenv my_app_venv |
1) 폴더 만들고 들어가서 2) 가상환경 만들기 |
source my_app_venv/bin/activate |
1) 가상환경 동작시키기 * 나올때는 deactivate |
* virtualenv 를
쓰려면 pip로 인스톨하는게 맞다.
sudo apt-get install nginx |
1) 설치후 브라우저로 VM의 external IP로 들어가면 nginx 화면을 볼 수 있다. |
pip install gunicorn |
1) Web Application Server 를 동작시킬 gunicorn
실행 |
VM의 설정 (Edit 화면)
|
적용된 tag를 가진
Firewall rule
|
* [참고] apt-get 과 pip의 차이점은?
간단하게 이야기하면 - 특정 버전의 패키지를 특정 환경
안에만 (=virtualenv) 설치하려면 pip apt-get: Single version of package Limited number of modules pip: All modules Can select which package versions to install |
자세한 버전 : https://askubuntu.com/questions/431780/apt-get-install-vs-pip-install
|
* [참고] Google Cloud Shell 에서 깔면 날아가려나?
- 1시간 안쓰면 terminate 되고
- $HOME 디렉토리
외부의 변경사항은 다 날아간다
- 링크: https://cloud.google.com/shell/docs/limitations
_
간단한 파이썬앱을
만들어서 Gunicorn으로 실행시켜보자
gunicorn --workers 4 --bind 127.0.0.2:8080
myapplication
- 우리가 만든 myapplication.py 앱을 실행하고
- master process를
하나 실행한 다음 worker process를 4개 생성하였다 = pre-fork
- (중요) 현재 gunicorn이 실행된 VM의
- 0.0.0.0 으로
설정한다면어떤 내부 아이피로 들어오든 (=0.0.0.0)
- 혹은 특정하여 127.0.0.2의
8080 포트로 들어오는 놈은 이걸로 처리하겠다는 것이다.
* application 이라는 함수명은 default라
생략가능한것인가 보다.
- 만약 함수명이 myapp() 이라면 아래와 같이 gunicorn 실행
gunicorn --workers 4 --bind 127.0.0.2:8080
myapplication:myapp
[참고] workers
- 의미는 같은 기능을
하는 프로세스를 여러 개 띄운다는 것이다.
- 적정 worker 개수 공식은 아래와 같다.
1) 하나의 CPU에 두개의 worker를 할당해준다
2) 하나의 worker가 CPU와 일하다가
I/O쪽을 처리하면 CPU는 놀게된다
- 이때 다른 하나의 worker 가 CPU를 사용하라는 의미이다
그런데 VM의 External IP를 통해
8080 포트로 진입하면 접근이 안된다
해법 - 이라기 보다는 방법은 2가지가 있다
1) tcp:8080 포트를
열어주거나
(오류수정) 이대로는 안된다.
gunicorn 에서 --bind 0.0.0.0:8080 으로
해줘야 외부포트 8080을 열어줬을때 들어온다
2) 이미 열려있는, 그래서 Nginx 가 쓰고있는
tcp:80 포트로 들어온 것을
- Nginx 가 8080 포트로 redirection 해주는 거다.
Firewall (방화벽) 의 개념을 익힐겸 잠시 샛길로 돌아가보자.
1) Firewall rule - 방화벽 규칙을 하나 만든다음
- tcp:8080 을
허용한다
- tag 는 http-8080 으로 한다
2) 이것을 내 VM에 적용해주면 된다
|
Firewall rule - 방화벽 규칙을 만들었다. 1) 이름은 wsgi-8080 2) Ingress 들어오는 놈들 처리 3) http-8080 이라는 태그를 붙이기만 하면 방화벽 규칙 적용됨 마지막으로 적용할 프로토콜/포트는 tcp:80 - 왼쪽에 다 담기
길어서 여기에 복붙
그리고는 적용할 내 VM 에서 Network tags에 - http-8080만
추가하면 끝 - tcp:80 = http-server 는 이미 적용되어 있음 - 그러니깐 Nginx 가 access 된 것임
|
이렇게 해주고
다시 내 VM의 External IP 의 8080 포트로 들어가보면 연결된다!
- 현 시점에 두 개의
웹서버(?)가 돌고 있는 셈이다
- 즉, gunicorn 도 웹서버로 쓸 수 있다. 하지만 Nginx 의 여러 장점들을 쓰려고 양보하는 것이다
현시점에서 중간 정리
1) Nginx 와 Gunicorn 을 설치했고 2) Nginx 는 80 포트로 들어오는 HTTP 요청을 받아서 처리하는 웹서버 역할을
하고 있고 3) Gunicorn 은 127.0.0.2 IP의 (모든
IP라면 0.0.0.0 으로), 8080 포트로
들어오는 모든 HTTP 요청을 받아서 - 특정 어플리케이션이
처리하도록 하였으며 - 어플리케이션은 pre-fork 하여 4개를 두었다. * 위에 샛길로
빠진 방화벽 옵션은 제거하자
GCP의 방화벽 이야기 조금 더
- VM에 적용된 8080 방화벽을 제거하면 Gunicorn 으로 구동되는 웹서버(?)로는 더 이상 접근이 안될까? - Firewall rule 만 제거해도 Gunicorn 8080 포트가 동작하지 않는게 맞다. - 그런데 동작함. @_@ - 알고보니 http-8080 이라는 같은 이름의 태그를 적용한
firewall-rule 이 있었다. (정책이 좀 이상함) - 즉, firewall1, firewall2 라는 다른 이름의 firewall
rule 이 같은 포트를 열어주고, 같은 태그를 사용하는 경우도 가능하다는 것 |
/etc/nginx/nginx.conf 만 만져주면 된다.
- 127.0.0.1:8080 으로 실행하게 해둠
/etc/nginx/sites-available 폴더 - 처음엔 templete로 default 라는 파일 하나만 있음 - 모든 서버설정 파일들을
여기에 만들어둔다. - 하지만 이시점에는 nginx 서버와는 아무 연관없음 /etc/nginx/sites-enabled 폴더 - sites-available 폴더에서 실제 설정하고싶은 서버파일만 여기로 symlink 한다 * symlink 하면
- 여기에 파일이 있는것처럼
동작하지만 실제파일은 sites-available에 있는거다. - 여기서 symlink 한 파일을 삭제해도 원본은 삭제되지 않는다. - ngnix.conf 파일에서는
이 폴더의 파일들을 include 하여 설정에 적용하게 된다. - 결과적으로 sites-available 폴더의 서버 설정 파일들중 원하는 설정만을
symlink를 통해 enable / disable 하는 셈 /etc/nginx/nginx.conf 파일 - nginx 서버의
메인 설정파일 - include /etc/nginx/sites-enabled/*; 명령으로 서버정보를 끼워넣는다. (include) |
|
- 아래는 가벼운 실험
1) sites-available / sites-enabled 모두
default 라는 파일이 있다.
2) 사실 sites-enabled의 default 파일은 sites-available의 default 파일의 symlink 이다.
3) 따라서 sites-enabled 의 default 파일을 지워도 sites-available 의 파일은
지워지지 않는다.
- 외부 IP의 80 포트로 들어오는
request 는 nginx 로 오게된다
- 이걸 받아서 내부아이피 127.0.0.1의 8080 포트로 보내게 하는거다.
1) sites-available 폴더에서 template 역할을 하는 default 를
복사해서 gunicorn_server 를 만들고
sudo cp ../sites-available/default
../sites-available/gunicorn_server
2) 그걸 수정한다음
sudo vim ../sites-available/gunicorn_server
3) sites-enabled 폴더에 symlink 를 만든다
sudo ln -s ../sites-available/gunicorn_server
./gunicorn_server
뭘 수정했냐면
이렇게 했더니 에러발생
에러 내용을
보려면 아래와 같이 치면 된다고 하는데
systemctl status nginx.service
journalctl -xe
nginx -t -c /etc/nginx/nginx.conf 이걸 치면 문법적 오류 체크하는데 더 좋다
- 내 문제는 마지막에
세미콜론(;)을 안넣은것과 앞에 http:// 를 붙이지
않은 것이었다.
* upstream은 생략합니다
- sudo service nginx reload 해주면 재시작 해준다
- 이제 브라우저로 들어가보면 nginx 의 시작화면이 아니라 gunicorn을 통해 실행한 myapplication.py 파일이 실행될 것이다
- 참고로
nginx 시작: sudo service nginx start
nginx 정지: sudo service nginx stop
workers 에 따른 request 처리를 테스트 해보자
- boom: https://github.com/tarekziade/boom
- Google Cloud Shell 에서 pip install boom 으로 설치함. 안되면 위 링크 참고해서 install
boom http://104.198.203.36 -c 10 -n 100 |
1) 특정 ip로 request 폭탄을 날려라 2) -c: 동시에
(concurrency) 10개씩 3) -n: 총 100개를 날려라 |
- boom http://104.198.203.36 -c 10 -n 100
|
이거만 봐선 모르지만 - 위에건 gunicorn worker 가 1개 - 아래건 gunicorn worker 가 4개인 상황이다. 1) 둘 다 100번의 request 에 대해 모두 code 200 = OK 2) 둘다 시간차이도 거의 없다.
겨우 100개의 요청이라서 별 차이가 없는 것으로 보인다. - 어짜피 CPU 개수는 한정되어 있으니. |
참고. 만약 gunicorn 서버를 꺼버린다면? (Ctrl+C)
당연히 에러가
리턴된다. code 502 = Bad Gateway
- 아예 경로에 접근이 안되는 것
- boom http://104.198.203.36 -c
1000 -n 10000
- 부하를 좀 더 주니, 차이가 난다
- code 500 = Internal Server Error
도 보인다.
- 서버 폭주에 의한 서비스의
일시적 중단, 서버의 이상
- 서버 스트립트의 오류
workers = 1 - 그나마 응답이 온게 5226번이고 - 그중에서 3번은 code 500 |
workers = 4 - 9796번 응답이
왔고, 온 경우는 모두 code 200 - 응답이 안온 개수는 204 개 |
|
|
Microsoft OneNote 2016에서 작성